import { Component, OnInit, Host, ViewEncapsulation, Renderer2, ComponentFactoryResolver, Injector, ApplicationRef } from '@angular/core';
import { Store } from '@ngrx/store';

import InteractionDraw from 'ol/interaction/draw';
import InteractionSelect from 'ol/interaction/select';
import LayerVector from 'ol/layer/vector';
import SourceVector from 'ol/source/vector';
import Feature from 'ol/feature';
import GeomPolygon from 'ol/geom/polygon';
import GeomLineString from 'ol/geom/linestring';
import GeomPoint from 'ol/geom/point';
import Overlay from 'ol/overlay';
import OlObservable from 'ol/observable';
import Sphere from 'ol/sphere';
// import Style from 'ol/style/style';
// import StyleCircle from 'ol/style/circle';
// import StyleFill from 'ol/style/fill';
// import StyleStroke from 'ol/style/stroke';
import FormatGeoJSON from 'ol/format/geojson';

import * as turfBuffer from 'turf-buffer';

import { MapComponent } from '../../../ngx-ol/components/map/map.component';
import { MapState, DrawTool, BufferOptions, InputMarkOptions } from '../../store';
import * as fromDrawToolSelector from '../../store/selectors/draw-tool.selector';
import { InteractionDrawType } from './interaction-draw.model';
import { MapCursor } from '../../../ngx-ol/store/actions/map.action';
import { MapCursorTypes } from '../../../ngx-ol/enums';
import { DrawToolEnabled, DrawToolTypes, DrawToolDisabled } from '../../store/actions/draw-tool.action';
import { DrawSelectedStoreService } from '../../services';
import { LayersSpatialQueryUnuseable, LayersSpatialQueryUseable } from '../../store/actions/layers.action';
import { DomPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
import { DrawingFeaturePopoverComponent } from '../drawing-feature-popover/drawing-feature-popover.component';
import { DrawingFeaturePopoverSubject } from './drawing-feature-popover-ref';
import { PopoverProperty } from '../drawing-feature-popover/drawing-feature-popover.model';
import { StyleOptions, SimStyle, MarkerSize, SimStyleLine, SimStylePoint, SimStylePolygon } from '../../class';


@Component({
  selector: 'ege-interaction-draw',
  templateUrl: './interaction-draw.component.html',
  styleUrls: ['./interaction-draw.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class InteractionDrawComponent implements OnInit {

  instance: any;
  layer: any;
  type: InteractionDrawType;

  interactionSelect: any;

  private tooltipElement: HTMLElement;
  private tooltip: any;
  private helpElement: HTMLElement;
  private measureElement: HTMLElement;
  private sketch: any;
  private sketchListener: any;

  private measureAvailable: boolean = true;
  private selectedFeatures: any[] = [];
  private popover: any;
  private popoverPortalOutlet: DomPortalOutlet;

  private fid: number = 0;

  constructor(
    @Host() private mapComponent: MapComponent,
    private store: Store<MapState>,
    private renderer: Renderer2,
    private drawSelectedStroeService: DrawSelectedStoreService,
    private componentFactoryResolver: ComponentFactoryResolver,
    private injector: Injector,
    private appRef: ApplicationRef
  ) { }

  ngOnInit() {
    this.layer = this.layerBuilder();
    this.mapComponent.addLayer(this.layer);
    this.interactionSelect = this.interactionSelectBuilder(this.layer);
    this.mapComponent.instance.on('pointermove', event => this.pointerMoveHandler(event));
    this.store.select(fromDrawToolSelector.getInteractionDrawType).subscribe(
      (type: InteractionDrawType) => this.drawTypeChangeListener(type));
    this.store.select(fromDrawToolSelector.getDrawToolMeasureAvailable).subscribe((measureAvailable: boolean) => this.measureAvailable = measureAvailable);
    this.store.select(fromDrawToolSelector.getDrawToolBufferOptions).subscribe((buffer: BufferOptions) => this.bufferFeatures(this.selectedFeatures, buffer))
    this.store.select(fromDrawToolSelector.getDrawToolRemove).subscribe((remove: DrawTool) => remove.active && this.removeFeatures(this.selectedFeatures));
    this.store.select(fromDrawToolSelector.getDrawToolClean).subscribe((clean: DrawTool) => clean.active && this.clearCanvas());
    this.store.select(fromDrawToolSelector.getDrawToolInputMarkOptions).subscribe((lnglat: InputMarkOptions) => this.addPoint(lnglat));
    this.popover = this.ceratePopover();
    this.mapComponent.instance.on('click', (event) => setTimeout(() => this.clickMapListener(event), 260));
  }

  private drawTypeChangeListener(type: InteractionDrawType): void {
    this.type = type;
    this.removeInstance();
    type && this.instanceBuilder(type);
    if(type === InteractionDrawType.LINE 
      || type === InteractionDrawType.CIRCLE 
      || type === InteractionDrawType.RECTANGLE 
      || type === InteractionDrawType.POLYGON) {
      this.store.dispatch(new MapCursor(MapCursorTypes.CROSSHAIR));
    } else {
      this.store.dispatch(new MapCursor(MapCursorTypes.DEFAULT));
    }
  }

  private layerBuilder(): any {
    const styleOptions: StyleOptions = {
      point: {
        "marker-color": '#222f3e',
        "marker-size": MarkerSize.medium,
        "marker-symbol": null
      },
      line: {
        "stroke": '#222f3e',
        "stroke-width": 2,
        "stroke-opacity": 1
      },
      polygon: {
        "stroke": '#222f3e',
        "stroke-width": 2,
        "stroke-opacity": 1,
        "fill": '#222f3e',
        "fill-opacity": 0.5
      }
    };
    return new LayerVector({
      source: new SourceVector(),
      style: this.styleBuilder(styleOptions),
      zIndex: 10001
    });
  }

  private styleBuilder(options: StyleOptions): any {
    const style = new SimStyle(options);
    return style.getStyleAsOl();
  }

  private bufferFeatures(features: any[], options: BufferOptions): void {
    if(features.length > 0 && options.value > 0) {
      const source = this.layer.getSource();
      const selectedFeatures = this.interactionSelect.getFeatures();
      const bufferedFeatures = features.map(feature => {
        let buffered = this.bufferFeature(feature, options.unit, options.value);
        let id = `FID_null`;
        selectedFeatures.push(buffered);
        selectedFeatures.remove(feature);
        this.drawSelectedStroeService.remove(feature);
        if(!options.clone) {
          source.removeFeature(feature);
          id = feature.getId();
        } else {
          this.fid++;
          id = `FID_${this.fid}`
        }
        buffered = this.addProperties(buffered, { id });
        buffered.setId(id);
        return buffered;
      });
      source.addFeatures(bufferedFeatures);
      this.drawSelectedStroeService.add(bufferedFeatures);
      this.selectedFeatures = bufferedFeatures;
      this.hiddenPopover();
    }
  }

  private bufferFeature(feature: any, unit: string, buffer: number): any {
    const format = new FormatGeoJSON();
    let pt = format.writeFeatureObject(feature, { featureProjection: 'EPSG:3857' });
    let buffered = turfBuffer(pt, buffer, unit);
    return format.readFeature(buffered, { featureProjection: 'EPSG:3857' });
  }

  private removeFeatures(features: any[]): void {
    if(this.layer && features.length > 0) {
      const source = this.layer.getSource();
      features.forEach(feature => source.removeFeature(feature));
      this.interactionSelect.getFeatures().clear();
      this.drawSelectedStroeService.remove(features);
      this.selectedFeatures = [];
      this.store.dispatch(new DrawToolDisabled(DrawToolTypes.BUFFER));
      this.store.dispatch(new DrawToolDisabled(DrawToolTypes.REMOVE));
      this.store.dispatch(new LayersSpatialQueryUnuseable());
    }
  }

  private clearCanvas(): void {
    this.layer && this.layer.getSource().clear();
    this.interactionSelect.getFeatures().clear();
    this.drawSelectedStroeService.remove(this.selectedFeatures);
    this.selectedFeatures = [];
    this.store.dispatch(new DrawToolDisabled(DrawToolTypes.BUFFER));
    this.store.dispatch(new DrawToolDisabled(DrawToolTypes.REMOVE));
    this.store.dispatch(new LayersSpatialQueryUnuseable());
  }

  private interactionSelectBuilder(layer: any): void {
    if(this.interactionSelect) {
      this.mapComponent.removeInteraction(this.interactionSelect);
    }
    const styleOptions: StyleOptions = {
      point: {
        "marker-color": '#2e86de',
        "marker-size": MarkerSize.medium,
        "marker-symbol": null
      },
      line: {
        "stroke": '#2e86de',
        "stroke-width": 4,
        "stroke-opacity": 1
      },
      polygon: {
        "stroke": '#2e86de',
        "stroke-width": 4,
        "stroke-opacity": 1,
        "fill": '#2e86de',
        "fill-opacity": 0.5
      }
    };
    const interaction = new InteractionSelect({
      layers: [layer],
      style: this.styleBuilder(styleOptions)
    });
    interaction.on('select', event => this.selectedHandler(event));
    this.store.select(fromDrawToolSelector.getDrawToolSelect).subscribe((select: DrawTool) => interaction.setActive(select.active));
    this.mapComponent.addInteraction(interaction);
    return interaction;
  }

  private selectedHandler(event): void {
    const deselected = event.deselected;
    const selected = event.selected;
    this.drawSelectedStroeService.remove(deselected);
    this.drawSelectedStroeService.add(selected);
    this.selectedFeatures = [...this.selectedFeatures, ...selected].filter(feature => !deselected.includes(feature));
    if(this.selectedFeatures.length > 0) {
      this.store.dispatch(new DrawToolEnabled(DrawToolTypes.BUFFER));
      this.store.dispatch(new DrawToolEnabled(DrawToolTypes.REMOVE));
      this.store.dispatch(new LayersSpatialQueryUseable());
    } else {
      this.store.dispatch(new DrawToolDisabled(DrawToolTypes.BUFFER));
      this.store.dispatch(new DrawToolDisabled(DrawToolTypes.REMOVE));
      this.store.dispatch(new LayersSpatialQueryUnuseable());
    }
  }

  private instanceBuilder(type: InteractionDrawType): void {
    const source = this.layer.getSource();
    let geometryFunction = undefined;
    if(type === InteractionDrawType.CIRCLE) {
      geometryFunction = InteractionDraw.createRegularPolygon(128);
    } else if(type === InteractionDrawType.RECTANGLE) {
      type = InteractionDrawType.CIRCLE;
      geometryFunction = InteractionDraw.createBox();
    }
    this.instance = new InteractionDraw({ 
      type, 
      source, 
      geometryFunction, 
      style: (feature) => this.drawingStyleBuilder(feature)
    });
    this.instance.on('drawstart', event => this.drawstartListener(event));
    this.instance.on('drawend', event => this.drawendListener(event));
    this.mapComponent.addInteraction(this.instance);
    this.createTooltip();
  }

  private drawstartListener(event): void {
    let tooltipCoord = event.coordinate;
    this.sketch = event.feature;
    this.sketchListener = this.sketch.getGeometry().on('change', (event) => {
      const geom = event.target;
      let output;
      if (geom instanceof GeomPolygon) {
        output = this.formatArea(geom);
        tooltipCoord = geom.getInteriorPoint().getCoordinates();
      } else if (geom instanceof GeomLineString) {
        output = this.formatLength(geom);
        tooltipCoord = geom.getLastCoordinate();
      }
      if(this.measureAvailable && this.type !== InteractionDrawType.POINT) {
        if(!this.measureElement) {
          this.measureElement = document.createElement('span');
          this.renderer.addClass(this.measureElement, 'draw-tooltip-subtext');
          this.tooltipElement.insertBefore(this.measureElement, this.helpElement);
          this.renderer.removeClass(this.tooltipElement, 'draw-tooltip-single');
        }
        this.measureElement.innerHTML = output;
      }
    });
  }

  private drawendListener(event): void {
    const feature = event.feature;
    this.fid++;
    this.sketch = null;
    this.addProperties(feature, { id: `FID_${this.fid}` });
    feature.setId(`FID_${this.fid}`);
    if(this.measureElement) {
      this.measureElement.parentElement.removeChild(this.measureElement);
      this.measureElement = null;
      this.renderer.addClass(this.tooltipElement, 'draw-tooltip-single');
    }
    OlObservable.unByKey(this.sketchListener);
  }

  private addProperties(feature, initial: { [key: string]: any } = {}): any {
    const geometry = feature.clone().getGeometry();
    let properties = { ...initial };
    if(geometry.getType() === 'Point') {
      const point = geometry.transform('EPSG:3857', 'EPSG:4326');
      const coordinate = geometry.getCoordinates();
      properties['lng'] = coordinate[0];
      properties['lat'] = coordinate[1];
    } else if(geometry.getType() === 'LineString') {
      properties['length'] = Sphere.getLength(geometry);
    } else if(geometry.getType() === 'Polygon') {
      properties['area'] = Sphere.getArea(geometry);
    }
    feature.setProperties(properties);
    return feature;
  }

  private formatArea(polygon): any {
    const area = Sphere.getArea(polygon);
    let output;
    if (area > 10000) {
      output = (Math.round(area / 1000000 * 100) / 100) +
          ' ' + 'km<sup>2</sup>';
    } else {
      output = (Math.round(area * 100) / 100) +
          ' ' + 'm<sup>2</sup>';
    }
    return output;
  }

  private formatLength(line): any {
    const length = Sphere.getLength(line);
    let output;
    if (length > 100) {
      output = (Math.round(length / 1000 * 100) / 100) +
          ' ' + 'km';
    } else {
      output = (Math.round(length * 100) / 100) +
          ' ' + 'm';
    }
    return output;
  }

  private drawingStyleBuilder(feature): any {
    const geometry = feature.getGeometry();
    const geometryType = geometry.getType();
    let style: SimStyleLine|SimStylePoint|SimStylePolygon;
    if(geometryType === 'Point') {
      if(this.type === geometryType) {
        style = new SimStylePoint({
          "marker-color": '#ef5777',
          "marker-size": MarkerSize.medium,
          "marker-symbol": null
        });
      }
    } else if(geometryType === 'LineString') {
      style = new SimStyleLine({
        "stroke": '#ef5777',
        "stroke-width": 4,
        "stroke-opacity": 0.8
      });
    } else if(geometryType === 'Polygon') {
      style = new SimStylePolygon({
        "stroke": '#ef5777',
        "stroke-width": 4,
        "stroke-opacity": 0.8,
        "fill": '#ef5777',
        "fill-opacity": 0.4
      });
    }
    return style ? style.getStyleAsOl() : null;
  }

  private removeInstance(): void {
    if(this.instance) {
      this.mapComponent.removeInteraction(this.instance);
      this.instance = null;
    }
    this.destroyTooltip();
  }

  private createTooltip(): void {
    this.destroyTooltip();
    this.tooltipElement = document.createElement('div');
    this.tooltipElement.className = 'draw-tooltip';
    this.tooltip = new Overlay({
      element: this.tooltipElement,
      offset: [15, 0],
      positioning: 'center-left'
    });
    this.mapComponent.instance.addOverlay(this.tooltip);
  }

  private destroyTooltip(): void {
    if(this.tooltipElement) {
      this.tooltipElement.parentNode.removeChild(this.tooltipElement);
      this.tooltipElement = null;
    }
    if(this.tooltip) {
      this.mapComponent.instance.removeOverlay(this.tooltip);
      this.tooltip = null;
    }
    this.helpElement = null;
    this.measureElement = null;
  }

  private pointerMoveHandler(event): void {
    if (event.dragging || !this.tooltip || !this.tooltipElement) {
      return;
    }
    let helpMsg = this.type === InteractionDrawType.POINT ? '点击添加标记.' : '点击开始绘图.';
    if (this.sketch) {
      if (this.type === InteractionDrawType.CIRCLE) {
        helpMsg = '点击完成圆的绘制.';
      } else if (this.type === InteractionDrawType.LINE) {
        helpMsg = '点击继续绘制线条.';
      } else if (this.type === InteractionDrawType.RECTANGLE) {
        helpMsg = '点击完成矩形的绘制.';
      } else if (this.type === InteractionDrawType.POLYGON) {
        helpMsg = '点击继续绘制多边形.';
      }
    }
    if(!this.helpElement) {
      this.helpElement = document.createElement('span');
      this.tooltipElement.insertBefore(this.helpElement, null);
      this.renderer.addClass(this.tooltipElement, 'draw-tooltip-single');
    }
    this.helpElement.innerHTML = helpMsg;
    this.tooltip.setPosition(event.coordinate);
    this.renderer.setStyle(this.tooltipElement, 'visibility', 'inherit');
  }

  private addPoint(lnglat: InputMarkOptions): void {
    if(lnglat) {
      const view = this.mapComponent.instance.getView();
      const source = this.layer.getSource();
      const geometry = (new GeomPoint([lnglat.lng, lnglat.lat])).transform('EPSG:4326', 'EPSG:3857');
      this.fid++;
      const feature = this.addProperties(new Feature({ geometry}), { id: `FID_${this.fid}` });
      feature.setId(`FID_${this.fid}`);
      source.addFeature(feature);
      view.animate({
        center: geometry.getCoordinates(),
        zoom: 12,
        duration: 300
      });
    }
  }

  private ceratePopover(): any {
    const element = document.createElement('div');
    this.renderer.addClass(element, 'drawing-feature-popover');
    this.renderer.setStyle(element, 'position', 'relative');
    this.renderer.setStyle(element, 'width', 0);
    this.renderer.setStyle(element, 'height', 0);
    const popover = new Overlay({ element });
    this.mapComponent.instance.addOverlay(popover);
    this.popoverPortalOutlet = new DomPortalOutlet(element, this.componentFactoryResolver, this.appRef, this.injector);
    return popover;
  }

  private openPopover(feature: any, position: number[], offset: number[]): void {
    const properties = feature.getProperties();
    const data = this.serializeFeatureProperties({ ...properties });
    const injector = Injector.create([{
      provide: DrawingFeaturePopoverSubject,
      useValue: new DrawingFeaturePopoverSubject({
        properties: data,
        closeCallback: () => this.hiddenPopover(),
        removeCallback: () => {
          this.hiddenPopover();
          this.removeFeatures([feature]);
        }
      })
    }]);
    const portal = new ComponentPortal(DrawingFeaturePopoverComponent, null, injector);
    this.hiddenPopover();
    this.popoverPortalOutlet.attach(portal);
    this.popover.setPosition(position);
    this.popover.setOffset(offset);
  }

  private hiddenPopover(): void {
    this.popoverPortalOutlet.detach();
  }

  private clickMapListener(event): void {
    const originalEvent = event.originalEvent;
    const feature = this.interactionSelect.getFeatures().item(0);
    let position = event.coordinate;
    let offset = [0, 0];
    this.hiddenPopover();
    if(!originalEvent.shiftKey && !event.dragging && feature && this.interactionSelect.getActive()) {
      const geometry = feature.getGeometry().clone();
      if(geometry instanceof GeomPoint) {
        position = geometry.getCoordinates();
        offset = [0, -40];
      }
      this.openPopover(feature, position, offset);
    };
  }

  private serializeFeatureProperties(properties: { [key: string]: any }): PopoverProperty[] {
    const alias: { [key: string]: any } = {
      length: '长度',
      area: '面积',
      lng: '经度',
      lat: '纬度',
      id: '编号'
    };
    const { ['geometry']: geometry, ['bbox']: bbox, ...data } = properties;
    return Object.keys(data).map(key => {
      let value = data[key];
      let unit = null;
      if(key === 'length') {
        if (value > 100) {
          value = (Math.round(value / 1000 * 100) / 100);
          unit= 'km';
        } else {
          value = (Math.round(value * 100) / 100);
          unit = 'm';
        }
      }
      if(key === 'area') {
        if (value > 10000) {
          value = (Math.round(value / 1000000 * 100) / 100);
          unit = 'sqkm';
        } else {
          value = (Math.round(value * 100) / 100);
          unit = 'sqm';
        }
      }
      return {
        name: key,
        alias: alias[key] || key,
        value, unit
      }
    });
  }

  

}
